diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-04 08:31:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-04 08:31:31 +0000 |
| commit | b67e36df49f067cbd5ba899f9fbcc755f38d4b4f (patch) | |
| tree | 5a71c5960f90d988cd509e3ef26bff497a277661 /app/[lng]/admin/edp-progress/page.tsx | |
| parent | b7f54b06c1ef9e619f5358fb0a5caad9703c8905 (diff) | |
(대표님, 최겸, 임수민) 작업사항 커밋
Diffstat (limited to 'app/[lng]/admin/edp-progress/page.tsx')
| -rw-r--r-- | app/[lng]/admin/edp-progress/page.tsx | 488 |
1 files changed, 96 insertions, 392 deletions
diff --git a/app/[lng]/admin/edp-progress/page.tsx b/app/[lng]/admin/edp-progress/page.tsx index 4efb739c..c42a1db7 100644 --- a/app/[lng]/admin/edp-progress/page.tsx +++ b/app/[lng]/admin/edp-progress/page.tsx @@ -2,430 +2,134 @@ import React from 'react'; import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { Separator } from '@/components/ui/separator'; import { ScrollArea } from '@/components/ui/scroll-area'; -import { - calculateVendorFormCompletion, - getProjectVendorCompletionSummary, - calculateVendorContractCompletion, - getVendorAllContractsCompletionSummary, - getAllVendorsContractsCompletionSummary, - getAllProjectsVendorCompletionSummary, - type VendorFormCompletionStats, - type ProjectVendorCompletionSummary, - type VendorAllContractsCompletionSummary -} from '@/lib/forms/vendor-completion-stats'; -import { Loader, TestTube, BarChart, FileText, TrendingUp } from 'lucide-react'; +import { getAllVendorsContractsCompletionSummary } from '@/lib/forms/vendor-completion-stats'; +import { Loader, Users, RefreshCw } from 'lucide-react'; import { toast } from 'sonner'; -interface TestResult { - type: string; - data: VendorFormCompletionStats | ProjectVendorCompletionSummary | VendorAllContractsCompletionSummary | unknown; +interface VendorProgress { + vendorId: number; + vendorName: string; + totalForms: number; + tagCount: number; + totalRequiredFields: number; + totalFilledFields: number; + completionPercentage: number; } export default function EDPProgressTestPage() { - const [loading, setLoading] = React.useState<string | null>(null); - const [results, setResults] = React.useState<TestResult | null>(null); - - // Form inputs - const [contractItemId, setContractItemId] = React.useState('123'); - const [formCode, setFormCode] = React.useState('SPR_LST'); - const [projectId, setProjectId] = React.useState('1'); - const [vendorId, setVendorId] = React.useState('1'); + const [loading, setLoading] = React.useState(false); + const [vendorProgress, setVendorProgress] = React.useState<VendorProgress[]>([]); - const handleTest = async (testType: string, testFunction: () => Promise<unknown>) => { - setLoading(testType); - setResults(null); + const loadVendorProgress = async () => { + setLoading(true); try { - const result = await testFunction(); - setResults({ type: testType, data: result }); + const result = await getAllVendorsContractsCompletionSummary(); - if (result) { - toast.success(`${testType} 테스트 완료`); + if (result && result.vendors) { + const progressData: VendorProgress[] = result.vendors.map(vendor => ({ + vendorId: vendor.vendorId, + vendorName: vendor.vendorName, + totalForms: vendor.totalForms, + tagCount: vendor.totalTags, + totalRequiredFields: vendor.totalRequiredFields, + totalFilledFields: vendor.totalFilledFields, + completionPercentage: vendor.overallCompletionPercentage + })); + + setVendorProgress(progressData); + toast.success(`${progressData.length}개 벤더의 진척도를 불러왔습니다`); } else { - toast.warning(`${testType} 결과가 없습니다`); + toast.warning('벤더 데이터가 없습니다'); } } catch (error) { - console.error(`Error in ${testType}:`, error); - toast.error(`${testType} 테스트 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`); + console.error('Error loading vendor progress:', error); + toast.error(`벤더 진척도 로드 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`); } finally { - setLoading(null); + setLoading(false); } }; - const renderVendorFormStats = (stats: VendorFormCompletionStats) => ( - <div className="space-y-4"> - <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold text-green-600">{stats.completionPercentage}%</div> - <p className="text-sm text-muted-foreground">완성도</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold">{stats.totalFilledFields}</div> - <p className="text-sm text-muted-foreground">입력된 필드</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold">{stats.totalRequiredFields}</div> - <p className="text-sm text-muted-foreground">총 필드</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold">{stats.tagCount}</div> - <p className="text-sm text-muted-foreground">태그 수</p> - </CardContent> - </Card> - </div> - - <Card> - <CardHeader> - <CardTitle className="text-lg">태그별 세부 현황</CardTitle> - </CardHeader> - <CardContent> - <ScrollArea className="h-48"> - <div className="space-y-2"> - {stats.detailsByTag.map((tag, index) => ( - <div key={index} className="flex items-center justify-between p-2 border rounded"> - <span className="font-medium">{tag.tagNo}</span> - <div className="flex items-center gap-2"> - <Badge variant={tag.completionPercentage >= 80 ? "default" : tag.completionPercentage >= 50 ? "secondary" : "destructive"}> - {tag.completionPercentage}% - </Badge> - <span className="text-sm text-muted-foreground"> - {tag.filledFields}/{tag.requiredFields} - </span> - </div> - </div> - ))} - </div> - </ScrollArea> - </CardContent> - </Card> - </div> - ); - - const renderProjectSummary = (summary: ProjectVendorCompletionSummary) => ( - <div className="space-y-4"> - <div className="grid grid-cols-2 md:grid-cols-3 gap-4"> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold text-blue-600">{summary.averageCompletionPercentage}%</div> - <p className="text-sm text-muted-foreground">평균 완성도</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold">{summary.totalVendors}</div> - <p className="text-sm text-muted-foreground">참여 벤더</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-lg font-bold">{summary.projectCode}</div> - <p className="text-sm text-muted-foreground">프로젝트 코드</p> - </CardContent> - </Card> - </div> - - <Card> - <CardHeader> - <CardTitle className="text-lg">벤더별 완성도</CardTitle> - </CardHeader> - <CardContent> - <ScrollArea className="h-48"> - <div className="space-y-2"> - {summary.vendors.map((vendor, index) => ( - <div key={index} className="flex items-center justify-between p-2 border rounded"> - <span className="font-medium">{vendor.vendorName}</span> - <Badge variant={vendor.completionPercentage >= 80 ? "default" : vendor.completionPercentage >= 50 ? "secondary" : "destructive"}> - {vendor.completionPercentage}% - </Badge> - </div> - ))} - </div> - </ScrollArea> - </CardContent> - </Card> - </div> - ); - - const renderVendorAllContracts = (summary: VendorAllContractsCompletionSummary) => ( - <div className="space-y-4"> - <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold text-purple-600">{summary.overallCompletionPercentage}%</div> - <p className="text-sm text-muted-foreground">전체 완성도</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold">{summary.totalContracts}</div> - <p className="text-sm text-muted-foreground">총 계약</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold">{summary.totalForms}</div> - <p className="text-sm text-muted-foreground">총 폼</p> - </CardContent> - </Card> - <Card> - <CardContent className="p-4"> - <div className="text-2xl font-bold">{summary.totalFilledFields}/{summary.totalRequiredFields}</div> - <p className="text-sm text-muted-foreground">입력 필드</p> - </CardContent> - </Card> - </div> - - <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> - <Card> - <CardHeader> - <CardTitle className="text-lg">프로젝트별 분석</CardTitle> - </CardHeader> - <CardContent> - <ScrollArea className="h-48"> - <div className="space-y-2"> - {summary.projectBreakdown.map((project, index) => ( - <div key={index} className="flex items-center justify-between p-2 border rounded"> - <div> - <div className="font-medium">{project.projectName}</div> - <div className="text-sm text-muted-foreground"> - 계약 {project.contractsCount}개, 폼 {project.formsCount}개 - </div> - </div> - <Badge variant={project.completionPercentage >= 80 ? "default" : "secondary"}> - {project.completionPercentage}% - </Badge> - </div> - ))} - </div> - </ScrollArea> - </CardContent> - </Card> - - <Card> - <CardHeader> - <CardTitle className="text-lg">계약별 세부 현황</CardTitle> - </CardHeader> - <CardContent> - <ScrollArea className="h-48"> - <div className="space-y-2"> - {summary.contracts.map((contract, index) => ( - <div key={index} className="flex items-center justify-between p-2 border rounded"> - <div> - <div className="font-medium">{contract.itemName}</div> - <div className="text-sm text-muted-foreground"> - {contract.projectName} - 폼 {contract.totalForms}개 - </div> - </div> - <Badge variant={contract.averageCompletionPercentage >= 80 ? "default" : "secondary"}> - {contract.averageCompletionPercentage}% - </Badge> - </div> - ))} - </div> - </ScrollArea> - </CardContent> - </Card> - </div> - </div> - ); + React.useEffect(() => { + loadVendorProgress(); + }, []); return ( <div className="container mx-auto p-6 space-y-6"> - <div className="flex items-center gap-2 mb-6"> - <TestTube className="h-6 w-6" /> - <h1 className="text-3xl font-bold">EDP Progress 서버 액션 테스트</h1> + <div className="flex items-center justify-between mb-6"> + <div className="flex items-center gap-2"> + <Users className="h-6 w-6" /> + <h1 className="text-3xl font-bold">벤더 진척도 현황</h1> + </div> + <Button + onClick={loadVendorProgress} + disabled={loading} + className="flex items-center gap-2" + > + {loading ? <Loader className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />} + 새로고침 + </Button> </div> - {/* Input Parameters */} + {/* Vendor Progress List */} <Card> <CardHeader> - <CardTitle className="flex items-center gap-2"> - <FileText className="h-5 w-5" /> - 테스트 파라미터 - </CardTitle> - <CardDescription> - 아래 값들을 수정하여 다양한 시나리오를 테스트할 수 있습니다. - </CardDescription> + <CardTitle>벤더별 작업 진척도</CardTitle> </CardHeader> <CardContent> - <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> - <div className="space-y-2"> - <Label htmlFor="contractItemId">Contract Item ID</Label> - <Input - id="contractItemId" - value={contractItemId} - onChange={(e) => setContractItemId(e.target.value)} - placeholder="123" - /> + {loading ? ( + <div className="flex items-center justify-center py-8"> + <Loader className="h-6 w-6 animate-spin mr-2" /> + <span>벤더 진척도를 불러오는 중...</span> </div> - <div className="space-y-2"> - <Label htmlFor="formCode">Form Code</Label> - <Input - id="formCode" - value={formCode} - onChange={(e) => setFormCode(e.target.value)} - placeholder="SPR_LST" - /> + ) : vendorProgress.length === 0 ? ( + <div className="text-center py-8 text-muted-foreground"> + 벤더 데이터가 없습니다. </div> + ) : ( <div className="space-y-2"> - <Label htmlFor="projectId">Project ID</Label> - <Input - id="projectId" - value={projectId} - onChange={(e) => setProjectId(e.target.value)} - placeholder="1" - /> - </div> - <div className="space-y-2"> - <Label htmlFor="vendorId">Vendor ID</Label> - <Input - id="vendorId" - value={vendorId} - onChange={(e) => setVendorId(e.target.value)} - placeholder="1" - /> + {/* Header */} + <div className="grid grid-cols-6 gap-4 p-3 bg-muted rounded-lg font-semibold text-sm"> + <div>벤더명</div> + <div className="text-center">폼 개수</div> + <div className="text-center">태그 개수</div> + <div className="text-center">전체 필드</div> + <div className="text-center">입력 필드</div> + <div className="text-center">완성도</div> + </div> + + {/* Vendor Rows */} + <ScrollArea className="h-96"> + <div className="space-y-1"> + {vendorProgress.map((vendor) => ( + <div key={vendor.vendorId} className="grid grid-cols-6 gap-4 p-3 border rounded-lg hover:bg-muted/50"> + <div className="font-medium">{vendor.vendorName}</div> + <div className="text-center">{vendor.totalForms}</div> + <div className="text-center">{vendor.tagCount}</div> + <div className="text-center">{vendor.totalRequiredFields}</div> + <div className="text-center">{vendor.totalFilledFields}</div> + <div className="text-center"> + <Badge + variant={ + vendor.completionPercentage >= 80 ? "default" : + vendor.completionPercentage >= 50 ? "secondary" : + "destructive" + } + > + {vendor.completionPercentage}% + </Badge> + </div> + </div> + ))} + </div> + </ScrollArea> </div> - </div> + )} </CardContent> </Card> - - {/* Test Buttons */} - <Card> - <CardHeader> - <CardTitle className="flex items-center gap-2"> - <BarChart className="h-5 w-5" /> - 테스트 액션들 - </CardTitle> - </CardHeader> - <CardContent> - <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> - <Button - onClick={() => handleTest('vendor-form', () => - calculateVendorFormCompletion(Number(contractItemId), formCode) - )} - disabled={loading !== null} - className="h-auto p-4 flex flex-col items-start space-y-2" - > - {loading === 'vendor-form' && <Loader className="h-4 w-4 animate-spin" />} - <div className="font-semibold">벤더 폼 완성도</div> - <div className="text-sm opacity-80">특정 벤더의 특정 폼 완성도</div> - </Button> - - <Button - onClick={() => handleTest('project-summary', () => - getProjectVendorCompletionSummary(Number(projectId), formCode) - )} - disabled={loading !== null} - variant="outline" - className="h-auto p-4 flex flex-col items-start space-y-2" - > - {loading === 'project-summary' && <Loader className="h-4 w-4 animate-spin" />} - <div className="font-semibold">프로젝트 요약</div> - <div className="text-sm opacity-80">프로젝트의 모든 벤더 완성도</div> - </Button> - - <Button - onClick={() => handleTest('vendor-contract', () => - calculateVendorContractCompletion(Number(vendorId), Number(contractItemId)) - )} - disabled={loading !== null} - variant="outline" - className="h-auto p-4 flex flex-col items-start space-y-2" - > - {loading === 'vendor-contract' && <Loader className="h-4 w-4 animate-spin" />} - <div className="font-semibold">벤더 계약 완성도</div> - <div className="text-sm opacity-80">특정 벤더의 특정 계약 완성도</div> - </Button> - - <Button - onClick={() => handleTest('vendor-all-contracts', () => - getVendorAllContractsCompletionSummary(Number(vendorId)) - )} - disabled={loading !== null} - variant="secondary" - className="h-auto p-4 flex flex-col items-start space-y-2" - > - {loading === 'vendor-all-contracts' && <Loader className="h-4 w-4 animate-spin" />} - <div className="font-semibold">벤더 전체 계약</div> - <div className="text-sm opacity-80">벤더의 모든 계약 완성도</div> - </Button> - - <Button - onClick={() => handleTest('all-vendors', () => - getAllVendorsContractsCompletionSummary() - )} - disabled={loading !== null} - variant="secondary" - className="h-auto p-4 flex flex-col items-start space-y-2" - > - {loading === 'all-vendors' && <Loader className="h-4 w-4 animate-spin" />} - <div className="font-semibold">전체 벤더 요약</div> - <div className="text-sm opacity-80">모든 벤더의 계약 완성도</div> - </Button> - - <Button - onClick={() => handleTest('all-projects', () => - getAllProjectsVendorCompletionSummary() - )} - disabled={loading !== null} - variant="secondary" - className="h-auto p-4 flex flex-col items-start space-y-2" - > - {loading === 'all-projects' && <Loader className="h-4 w-4 animate-spin" />} - <div className="font-semibold">전체 프로젝트 요약</div> - <div className="text-sm opacity-80">모든 프로젝트의 벤더 완성도</div> - </Button> - </div> - </CardContent> - </Card> - - <Separator /> - - {/* Results */} - {results && ( - <Card> - <CardHeader> - <CardTitle className="flex items-center gap-2"> - <TrendingUp className="h-5 w-5" /> - 테스트 결과: {results.type} - </CardTitle> - </CardHeader> - <CardContent> - {!results.data ? ( - <div className="text-center py-8 text-muted-foreground"> - 데이터가 없습니다. 파라미터를 확인해주세요. - </div> - ) : results.type === 'vendor-form' ? ( - renderVendorFormStats(results.data as VendorFormCompletionStats) - ) : results.type === 'project-summary' ? ( - renderProjectSummary(results.data as ProjectVendorCompletionSummary) - ) : results.type === 'vendor-all-contracts' ? ( - renderVendorAllContracts(results.data as VendorAllContractsCompletionSummary) - ) : ( - <div className="space-y-4"> - <div className="bg-muted p-4 rounded-lg"> - <pre className="text-sm overflow-auto max-h-96"> - {JSON.stringify(results.data, null, 2)} - </pre> - </div> - </div> - )} - </CardContent> - </Card> - )} </div> ); -} +}
\ No newline at end of file |
